/**
 * @file i80_lcd_mono.c
 * @brief Monochrome LCD Controller with i80 interface
 *
 * @author yemt@witsi.cn
 * @date 2012-09-12
 * @version i80_lcd_mono.01
 */
#include "dev.h"
#include "gpio.h"
#include "kernel.h"
#include "iodev.h"
#include <string.h>
#include "ioctl.h"
#include "regs.h"
#include "config.h"
#include "running.h"

#define lcd_write(addr,val) do{ *((volatile uint8_t *)(addr)) = (val); }while(0)

#define LCD_BUF_LEN ((CONFIG_LCD_W * CONFIG_LCD_H) / 8) /**< LCD Framebuffer Length */

static uint8_t lcd_fb[LCD_BUF_LEN];     /**< LCD Framebuffer */
static lcd_inf_t * lcd_inf;             /**< LCD Inferface */

extern uint8_t lcd_init_cmd[];          /**< LCD Initialization Commands */
extern uint8_t lcd_x_ofs;               /**< LCD x adjustment */
extern uint8_t lcd_y_ofs;               /**< LCD y adjustment */
 extern int lcd_disp_attribute(uint32_t cmd,void *arg);

 int lcd_write_reg(uint32_t reg)
{
//	printf("reg = 0x%x\n",reg);
	uint32_t cmd_addr = lcd_inf->inf.i80_lcd.i80_addr;

#ifdef CONFIG_LCD_A0_GPIO
	gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 0);
#endif
	lcd_write(cmd_addr, reg);
#ifdef CONFIG_LCD_A0_GPIO
	gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 1);
#endif

        return 0;
}

 int lcd_write_data(uint32_t x, uint32_t y, uint32_t data)
{
        uint32_t start, end, tmp;

        x += lcd_x_ofs;
        y += lcd_y_ofs;

        start = y >> 3;

        uint32_t cmd_addr = lcd_inf->inf.i80_lcd.i80_addr;
        uint32_t data_addr = cmd_addr;
#ifndef CONFIG_LCD_A0_GPIO
        data_addr |= (1 << lcd_inf->inf.i80_lcd.RS_bit);
#endif
        
#ifdef CONFIG_LCD_A0_GPIO
                gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 0);
#endif

                lcd_write(cmd_addr, 0xb0 | start);
                lcd_write(cmd_addr, 0x10 + (x >> 4));
                lcd_write(cmd_addr, (x & 0xf));

#ifdef CONFIG_LCD_A0_GPIO
                gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 1);
#endif
              lcd_write(data_addr, data);         
          
        return 0;
}

#ifdef CONFIG_YMC00122_TITLE

static uint32_t g_title = 0;

void title_control( uint32_t y, uint8_t flag)
{	
	uint8_t shift = (y >> 3) ? 8 : 0;

	if (flag)
		g_title |= 1 << y;
	else 
		g_title &= ~(1 << y);
	
	if (y < 12) {
		lcd_write_data(131, 32 + shift, (g_title >> (shift)) & 0xff);
	}
	else {
		y -= 12;
		shift = (y >> 3)?8:0;
		lcd_write_data(0, 32 + shift, ((g_title >> 12) >> (shift)) & 0xff);
	}
}

#endif

static int i80_lcd_mono_update(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
        uint32_t start, end, tmp;
        uint32_t i =0;
        x += lcd_x_ofs;
        y += lcd_y_ofs;

        start = y >> 3;
        end = ((y + h + 7) >> 3);
        uint32_t cmd_addr = lcd_inf->inf.i80_lcd.i80_addr;
        uint32_t data_addr = cmd_addr;
#ifndef CONFIG_LCD_A0_GPIO
        data_addr |= (1 << lcd_inf->inf.i80_lcd.RS_bit);
#endif
        while (start < end) {
#ifdef CONFIG_LCD_A0_GPIO
                gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 0);
#endif

                lcd_write(cmd_addr, 0xb0 | start);
                lcd_write(cmd_addr, 0x10 + (x >> 4));
                lcd_write(cmd_addr, (x & 0xf));

#ifdef CONFIG_LCD_A0_GPIO
                gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 1);
#endif
                tmp = CONFIG_LCD_W * start;
                for (i = x; i < x + w; i ++) {
                    lcd_write(data_addr, lcd_fb[tmp + i]);
                }
                start ++;
        }
        return 0;
}

static int i80_lcd_mono_init(iodev_t * dev)
{
        dev_inf_t * dinf = iodev_get_inf(dev);
        if (dinf == NULL) return -1;

        lcd_inf = (lcd_inf_t *)dinf->priv;
        if (lcd_inf->backlight_ctl) lcd_inf->backlight_ctl(1); /* Backlight ON */

        uint8_t cmd;
        int i = 0;
        uint32_t addr = lcd_inf->inf.i80_lcd.i80_addr;

#ifdef CONFIG_LCD_A0_GPIO
        gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 0);
#endif
        while ((cmd = lcd_init_cmd[i ++])) {
                if (cmd == 0xFF) mdelay(lcd_init_cmd[i ++]); /* Delay Required */
                else lcd_write(addr, cmd);
        }

#ifdef CONFIG_YMC00122_TITLE
	for (i = 0; i < 32; i ++) {
		title_control(i, 0);
	}
#endif

#ifdef CONFIG_H3D1_LCD_TITLE

        uint32_t data_addr = addr;

#ifndef CONFIG_LCD_A0_GPIO
        data_addr |= (1 << lcd_inf->inf.i80_lcd.RS_bit);
#endif
#ifdef CONFIG_LCD_A0_GPIO
        gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 0);
#endif

        lcd_write(addr, 0xb0 | 8);
        lcd_write(addr, 0x10);
        lcd_write(addr, 0);

#ifdef CONFIG_LCD_A0_GPIO
        gpio_output(lcd_inf->inf.i80_lcd.RS_bit, 1);
#endif
        for (i = 0; i < 128; i ++) {
            lcd_write(data_addr, 0);
        }
#endif
        return 0;
}

static int i80_lcd_mono_ioctl(iodev_t *dev, uint32_t cmd, void *arg)
{
        switch (cmd) {
        case IOCTL_LCD_GET_INFO:
                {
                        lcd_info_t * p = (lcd_info_t *)arg;
                        if (p) {
                                p->width = CONFIG_LCD_W;
                                p->height = CONFIG_LCD_H;
                                p->fbuf = lcd_fb;
                                p->fbuf_len = LCD_BUF_LEN;
                        }
                }
                break;
        case IOCTL_LCD_UPDATE:
                {
                        lcd_block_t * blk = (lcd_block_t *)arg;
                        if (blk) {
                                i80_lcd_mono_update(blk->x, blk->y, blk->w, blk->h);
                        }
                        else {
                                i80_lcd_mono_update(0, 0, CONFIG_LCD_W, CONFIG_LCD_H);
                        }
                }

                break;
        case IOCTL_LCD_BL:
                {
                        uint32_t onoff = (uint32_t)arg;
                        if (lcd_inf->backlight_ctl) lcd_inf->backlight_ctl(onoff);
                }
                break;
		default:
			if((cmd&IOCTL_LCD_ATTRIBUTE) == IOCTL_LCD_ATTRIBUTE) {
                      int ret = lcd_disp_attribute(cmd&0xff,arg);
                     return ret;
			} else {
				return -1;
			}
		}
        return 0;
}
#ifdef CONFIG_POWER_CTL
 static int i80_suspend(iodev_t *dev,int sleep)
 {
    if (sleep >= 2) {
        if (lcd_inf->backlight_ctl) lcd_inf->backlight_ctl(0);
    }
    return 0;
 }
static int i80_wakeup(iodev_t *dev, int sleep)
{
    if (sleep >= 2) {
        if (lcd_inf->backlight_ctl) lcd_inf->backlight_ctl(1);
    }
    return 0;
}
#endif

static const iodev_t lcd_i80 __iodev_entry = {
        .name = "lcd",
        .init = i80_lcd_mono_init,
        .read = NULL,
        .write = NULL,
        .ioctl = i80_lcd_mono_ioctl,
        .poll = NULL,
#ifdef CONFIG_POWER_CTL
		.suspend = i80_suspend,
		.wakeup = i80_wakeup,
 #endif

};

